winbrew_windows\deployment\msi/
directory.rs1use anyhow::{Context, Result, bail};
9use std::collections::{HashMap, HashSet};
10use std::path::{Path, PathBuf};
11
12use super::{DirectoryRow, path::select_msi_name};
13
14pub(super) fn resolve_directory_paths(
15 rows: &HashMap<String, DirectoryRow>,
16 install_root: &Path,
17) -> Result<HashMap<String, PathBuf>> {
18 let mut resolved = HashMap::new();
23 let mut visiting = HashSet::new();
24
25 for directory_id in rows.keys() {
26 let _ = resolve_directory_path(
27 directory_id,
28 rows,
29 &mut resolved,
30 &mut visiting,
31 install_root,
32 )?;
33 }
34
35 Ok(resolved)
36}
37
38fn resolve_directory_path(
39 directory_id: &str,
40 rows: &HashMap<String, DirectoryRow>,
41 resolved: &mut HashMap<String, PathBuf>,
42 visiting: &mut HashSet<String>,
43 install_root: &Path,
44) -> Result<PathBuf> {
45 if let Some(path) = resolved.get(directory_id) {
48 return Ok(path.clone());
49 }
50
51 if !visiting.insert(directory_id.to_string()) {
52 bail!("cycle detected in MSI Directory table at '{directory_id}'");
53 }
54
55 let row = rows
56 .get(directory_id)
57 .with_context(|| format!("missing MSI Directory row for '{directory_id}'"))?;
58
59 let base = match row.parent.as_deref() {
60 Some(parent) if !parent.is_empty() => {
61 resolve_directory_path(parent, rows, resolved, visiting, install_root)?
62 }
63 _ if directory_id.eq_ignore_ascii_case("TARGETDIR")
64 || directory_id.eq_ignore_ascii_case("SOURCEDIR") =>
65 {
66 install_root.to_path_buf()
67 }
68 _ => install_root.to_path_buf(),
69 };
70
71 let path = match select_msi_name(&row.default_dir) {
72 Some(segment) => base.join(segment),
73 None => base,
74 };
75
76 visiting.remove(directory_id);
77 resolved.insert(directory_id.to_string(), path.clone());
78
79 Ok(path)
80}